package app.v1.common.domain.pojo;

import app.common.ar.Ar;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.dtflys.forest.Forest;
import com.fasterxml.jackson.databind.type.ArrayType;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.gitee.feizns.quickstart.ddl.annotation.Comment;
import com.gitee.feizns.quickstart.jackson.JsonUtils;
import com.gitee.feizns.quickstart.mybatis.plus.builder.annotation.Equal;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import javax.persistence.Column;
import javax.persistence.Entity;
import java.time.LocalDateTime;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * 字典表
 * @author feizns
 * @since 2023/2/24
 */
@Entity
@Getter
@Setter
@ToString(callSuper = true)
@Accessors(chain = true)
@Comment("字典")
public class Dict extends Ar<Dict> {

    /**
     * 系统设置
     */
    public static final String systemSetting = "system-setting";

    /**
     * 名字
     */
    private String name;
    /**
     * 字典类型
     */
    @Equal
    @Comment("字典类型")
    private String type;

    /**
     * 字典键（唯一）
     */
    @Equal
    @Comment("字典键（唯一）")
    @Column(name = "`key`", unique = true)
    @TableField("`key`")
    private String key;

    /**
     * 字典值
     */
    @Comment("字典值")
    private String value;

    /**
     * 字典备注
     */
    @Comment("字典备注")
    private String comment;

    /**
     * 值类型（string=字符、integer=整数、number=数字、array=json数组、object=json对象、url=链接）
     */
    @Comment("值类型（string=字符、integer=整数、number=数字、array=json数组、object=json对象、url=链接）")
    @Column(columnDefinition = "VARCHAR(255) DEFAULT 'string'")
    private ValueType valueType;

    /**
     * 访问权限（readonly=只读、readwrite=可读写）
     */
    @Comment("访问权限（readonly=只读、readwrite=可读写）")
    @Column(columnDefinition = "VARCHAR(255) DEFAULT 'readwrite'")
    private AccessPermission accessPermission;

    /**
     * 字典值
     * @return {@link DictValue}
     */
    public DictValue value() {
        return new DictValue(this.value, this.valueType);
    }

    /**
     * dict值缓存管理器
     */
    private static final DictValueCacheManager hotDictValueCacheManager = new DictValueCacheManager();

    /**
     * 获取字典值
     * @param key 关键
     * @return {@link String}
     */
    public static DictValue value(String key) {
        DictValue dictValue = hotDictValueCacheManager.get(key);
        if ( dictValue == null ) {
            Dict dict = get(key, Dict::getValue, Dict::getValueType);
            if ( dict != null ) {
                dictValue = dict.value();
                hotDictValueCacheManager.put(key, dictValue);
            } else {
                dictValue = DictValue.NULL;
            }
        }
        return dictValue;
    }

    /**
     * 是否包含key
     * @param key 关键
     * @return boolean
     */
    public static boolean containsKey(String key) {
        return new Dict().setKey(key).exists();
    }

    /**
     * 获取对应键的字典
     * @param key 关键
     * @return {@link Dict}
     */
    @SafeVarargs
    public static Dict get(String key, SFunction<Dict, ?>... columns) {
        return new Dict().setKey(key).fetch().one(columns);
    }

    /**
     * 存储对应的值
     * @param key 关键
     */
    public static Dict put(String key, String value) {
        //查询字典编号和值
        Dict dict = Dict.get(key, Dict::getId, Dict::getValue, Dict::getAccessPermission);
        //确认字典是可写入的
        if ( dict != null ) {
            if ( !dict.isWritable() )
                throw new UnsupportedOperationException(String.format("字典['%s']不支持修改.", key));
        } else {
            //不存在创建默认的
            dict = Dict.ofDefault(key, value);
        }
        //插入或更新数据
        dict.insertOrUpdate();
        return dict;
    }

    /**
     * 删除指定键的字典
     * @param key 关键
     * @return {@link Dict}
     */
    public static Dict del(String key) {
        Dict dict = get(key, Dict::getAccessPermission);
        if ( dict != null && !dict.isWritable() )
            throw new UnsupportedOperationException(String.format("字典['%s']不支持删除.", key));
        //删除
        new Dict().setKey(key).delete();
        return dict;
    }

    /**
     * 是否是可写
     * @return boolean
     */
    public boolean isWritable() {
        return accessPermission == AccessPermission.readwrite;
    }

    /**
     * 默认字典
     * @param key 关键
     * @param value 价值
     * @return {@link Dict}
     */
    public static Dict ofDefault(String key, String value) {
        Dict dict = new Dict();
        dict.setKey(key);
        dict.setValue(value);
        dict.setValueType(ValueType.string);
        dict.setAccessPermission(AccessPermission.readwrite);
        return dict;
    }

    /**
     * 将任意值转换为字符串
     * @param value 价值
     * @return {@link String}
     */
    public static String toString(Object value) {
        if ( value instanceof String )
            return (String) value;
        return JsonUtils.toJson(value);
    }

    /**
     * 值类型（string=字符串、integer=整型、number=数字、array=数组、object=对象）
     * @author feizns
     * @since 2023/04/18
     */
    public enum ValueType {

        /**
         * 字符串
         */
        string,

        /**
         * 整数
         */
        integer,

        /**
         * 数量
         */
        number,

        /**
         * json list
         */
        array,

        /**
         * 对象
         */
        object,

        /**
         * url
         */
        url;

        /**
         * 值转换器
         */
        static final EnumMap<ValueType, Function<String, ?>> valueConverters = new EnumMap<>(ValueType.class);

        static {
            valueConverters.put(ValueType.string, Function.identity());
            valueConverters.put(ValueType.integer, Integer::valueOf);
            valueConverters.put(ValueType.number, Double::valueOf);
            valueConverters.put(ValueType.array, val -> JsonUtils.parse(val, List.class));
            valueConverters.put(ValueType.object, val -> JsonUtils.parse(val, Map.class));
            valueConverters.put(ValueType.url, url -> Forest.get(url).execute());
        }

        /**
         * 解析为指定类型的值
         * @param value 字符值
         * @return {@link Object}
         */
        public Object parse(String value) {
            return valueConverters.get(this).apply(value);
        }

    }

    /**
     * 访问权限
     * @author feizns
     * @since 2023/06/10
     */
    public enum AccessPermission {

        /**
         * 可读写
         */
        readwrite,

        /**
         * 只读
         */
        readonly;

    }

    /**
     * 字典值
     * @param value 值
     * @param valueType 值类型
     * @author feizns
     * @since 2023/06/10
     */
    public record DictValue(String value, ValueType valueType) {

        /**
         * NULL
         */
        public static final DictValue NULL = new DictValue(null, null);

        /**
         * 是否是null
         * @return boolean
         */
        public boolean isNull() {
            return value == null;
        }

        /**
         * 是否是字符串
         * @return boolean
         */
        public boolean isString() {
            return valueType == ValueType.string;
        }

        /**
         * 是否是数组
         * @return boolean
         */
        public boolean isArray() {
            return valueType == ValueType.array;
        }

        /**
         * 是否是对象
         * @return boolean
         */
        public boolean isObject() {
            return valueType == ValueType.object;
        }

        /**
         * 是否是json
         * @return boolean
         */
        public boolean isJson() {
            return isArray() || isObject();
        }

        /**
         * 作为布尔
         * @return boolean
         */
        public boolean asBoolean() {
            return Boolean.parseBoolean(value);
        }

        /**
         * 作为int
         * @return boolean
         */
        public int asInt() {
            return Integer.parseInt(value);
        }

        /**
         * 作为double
         * @return boolean
         */
        public double asDouble() {
            return Double.parseDouble(value);
        }

        /**
         * 作为字符串
         * @return boolean
         */
        public String asString() {
            return value;
        }

        /**
         * 正如列表
         * @param elementType 响应对象类
         * @return {@link List}<{@link T}>
         */
        public <T> List<T> asList(Class<T> elementType) {
            CollectionType collectionType = JsonUtils.mapperFu()
                    .map(mapper -> mapper.getTypeFactory().constructCollectionType(List.class, elementType))
                    .getParameter();
            return JsonUtils.parse(value, collectionType);
        }

        /**
         * 做为数组
         * @param elementType 响应对象类
         * @return {@link T[]}
         */
        public <T> T[] asArray(Class<T> elementType) {
            ArrayType arrayType = JsonUtils.mapperFu()
                    .map(mapper -> mapper.getTypeFactory().constructArrayType(elementType))
                    .getParameter();
            return JsonUtils.parse(value, arrayType);
        }

        /**
         * 作为对象
         * @param type 响应对象类
         * @return {@link T}
         */
        public <T> T asObject(Class<T> type) {
            return JsonUtils.parse(value, type);
        }

        /**
         * 原始对象
         * @return {@link Object}
         */
        public Object raw() {
            return valueType != null && value != null ? valueType.parse(value) : null;
        }

    }

    /**
     * dict缓存管理器
     * @author feizns
     * @since 2023/06/10
     */
    private static class DictValueCacheManager {

        /**
         * dict值缓存
         */
        private final Map<String, ExpireCache<DictValue>> dictValueCache = new ConcurrentHashMap<>();

        /**
         * 放置缓存
         * @param key 关键
         * @param value 价值
         * @return {@link DictValue}
         */
        public DictValue put(String key, DictValue value) {
            ExpireCache<DictValue> cache = dictValueCache.put(key, new ExpireCache<>(value));
            return cache != null ? cache.value : null;
        }

        /**
         * 得到缓存
         * @param key 关键
         * @return {@link DictValue}
         */
        public DictValue get(String key) {
            ExpireCache<DictValue> cache = dictValueCache.get(key);
            if ( cache != null && !cache.isExpired() )
                return cache.value;
            return null;
        }

    }

    /**
     * dict缓存
     * @author feizns
     * @since 2023/06/10
     */
    private static class ExpireCache<T> {

        /**
         * value
         */
        private final T value;

        /**
         * 过期时间（一分钟后）、可按需调整
         */
        private final LocalDateTime expiredTime = LocalDateTime.now().plusMinutes(1);

        public ExpireCache(T value) {
            this.value = value;
        }

        /**
         * 是否是过期
         * @return boolean
         */
        public boolean isExpired() {
            return LocalDateTime.now().isAfter(expiredTime);
        }

    }

}
